Chapter 9: Dictionaries
From book Python Programming (Problem solving, Packages and Libraries) published by McGraw Hill Education (India) Private limited.
By:
Note the following:-
Markdown and code cells (extension .ipynb) and then downloaded as html. If someone wants to "modify" or "extend' this document, you may ask for the original .ipynb file by sending me an e-mail at:- 999.anuraggupta@gmail.com9.2.1. Properties of a dictionary
The following shows that the order of the key-value pair in a dictionary does not matter:
# ---ON IDLE
>>> myDict1 = {'a':'apple', 'b':'baby', 'c': 'cat', 'd': 'dog'}
>>> myDict2 = {'c': 'cat', 'b': 'baby', 'a': 'apple', 'd': 'dog'}
>>> myDict1 == myDict2 # The two dictionaries are equivalent
True
The following code examples on IDLE clarify the concepts given in the book:
# ---ON IDLE
>>> myTupeDict ={('car','red'): 20, # OK. A tuple can be key
('car','blue'):10}
>>> myListDict = {['car','red']: 20, # Error. List cannot be key
['car','blue']:10}
Traceback (most recent call last):
File "<pyshell#8>", line 1, in<module>
myListDict = {['car','red']: 20,
TypeError: unhashable type: 'list'
>>>
Note: Since a dictionary is a collection but not a sequence, you have to use keys inside the square brackets, not the ‘index’ . In dictionary the index numbers are replaced by ‘keys’:-
# ---ON IDLE---
>>> myDict = {'a':'apple', 'b':'baby', 'c': 'cat', 'd': 'dog'}
>>> myDict['a']
'apple'
9.2.2. Concept of hashable
The following code shows that a dictionary object has an id but it is not hashable:-
# ---ON IDLE---
>>> myDict = {'a':'apple', 'b':'baby', 'c': 'cat', 'd': 'dog'}
>>> id (myDict) # A dictionary has an id()
36070624
>>> hash(myDict) # But it is not hashable
Traceback (most recent call last):
File "<pyshell#9>", line 1, in<module>
hash(myDict)
TypeError: unhashable type: 'dict'
9.2.4. Creating, initializing, accessing elements
You can create an empty dictionary. The following code shows how:
# ---ON IDLE---
>>> d1 = {}
>>> d2 = dict()
>>>print(d1, d2)
{} {}
There are many different ways to create a dictionary. Suppose you need to create a dictionary, say {'a':'apple', 'b':'baby', 'c': 'cat'}. We could do this in the following ways:
# ---ON IDLE---
>>> d = {'a': 'apple', 'b': 'baby', 'c': 'cat'}
>>> d1 = dict(a = 'apple', b = 'baby', c = 'cat')
>>> d2 = dict([('a', 'apple'), ('b', 'baby'), ('c', 'cat')])
>>> d3 = dict({'a': 'apple', 'b': 'baby', 'c': 'cat'})
>>> d == d1 == d2 == d3 # All are same
True
You can even create an empty dictionary and then add key-value pairs to it as shown:
# ---ON IDLE---
>>> d1 = dict() # Create an empty dictionary
>>> d1[1] = 'one'# Add an item
>>> d1['two'] = 2
>>> d1['three'] = 'three'
>>> d1 # Dislay the dictionary
{'two': 2, 1: 'one', 'three': 'three'}
9.3. Basic concepts 2
9.3.1. Dictionary comprehension
The general format for dictionary comprehension is as follows:-
# ---ON IDLE---
{key_expr: value_expr for var in iterable}
These are explained below:-
# ---ON IDLE---
key_expr: This is the expression which generates the key
value_expr: This is the expression which generates the ‘Value’
for var in: var is a temporary variable which stores each of the items in the iterable one by one.
Iterable: This is some iterable like a sequence or collection or range function etc.
Suppose you want to create a dictionary of whose keys are lowercase letters and whose values are the corresponding ASCII code. We could do this as follows:
# ---ON IDLE---
>>> d1 = { c: ord(c) for c in 'abcdef'}
>>> d1
{'d': 100, 'b': 98, 'e': 101, 'c': 99, 'f': 102, 'a': 97}
9.3.2. Checking for presence/ absence of a key in a dictionary
If you try to access a non-existent key in a dictionary, you will get an error as shown:
# ---ON IDLE---
>>> myDict = {'a':'apple', 'b':'baby', 'c': 'cat', 'd': 'dog'}
>>>print(myDict['e']) # Key ‘e’ does not exist in dictionary -> error
Traceback (most recent call last):
File "<pyshell#19>", line 1, in <module>
print(myDict['e'])
KeyError: 'e'
You can prevent the error by first checking if the key exists using the in operator . The following code shows how a function can be created to check for a key.
def hasKey(myD, k):
flag = k in myD
if flag:
print(myD, "has key ",k)
else:
print(myD, "doesnt have key ", k)
# Function call
d1 = {'a':'apple', 'b':'baby', 'c':'cat', 'd':'dog'}
hasKey(d1, 'a')
hasKey(d1, 'e')
There is another way of preventing an error if the key does not exist in the dictionary, that is, by using the get() method of the dictionary object. If the key value exists in the dictionary, the get() method returns it and if not it returns a value None.
# ---ON IDLE---
>>> D = {1: 'one', 'two': 2, 3 : 'three'}
>>> D.get(3)
'three'
>>> val = D.get(4) # Key 4 does not exist so return is None
>>>print(val)
None
A dictionary is mutable so you can add a new key-value pair to it. You can even modify an existing key-value pair or delete a key-value pair. The following example on IDLE explains the concepts:
# ---ON IDLE---
>>> myDict = {'a':'apple', 'b':'baby', 'c': 'cat', 'd': 'dog'}
>>> myDict['c'] = 'cow' # Change the value corresponding to key ‘c’
>>> myDict
{'c': 'cow', 'b': 'baby', 'a': 'apple', 'd': 'dog'}
9.3.3.Traversing a dictionary
Looping through a dictionary through its keys:
# ---ON IDLE---
>>> d1 = { # a popular format for writing key-val for dictionary
'key1' : 'val1',
'key2' : 'val2',
'key3' : 'val3'
}
>>>for k in d1:
print('key ',k,' has value ',d1[k])
key key3 has value val3
key key2 has value val2
key key1 has value val1
9.3.4. Duplicate keys are not allowed (But duplicate values are allowed).
In a key-value pair in a dictionary, there are no restrictions on the value. The value can be any standard Python object (including built-in data types, standard built-in Python objects or even user-defined objects. You cannot have duplicate keys. If you do try to have duplicate keys in an assignment to a dictionary, the last assignment will be retained and the earlier one will be dropped. For example:-
# ---ON IDLE---
>>> myDict3 = {'a':'apple', 'b':'baby', 'c': 'cat', 'd': 'dog','a': 'ant'}# key ‘a’ twice
>>> myDict3
{'c': 'cat', 'b': 'baby', 'a': 'ant', 'd': 'dog'} #First assignment to ‘a’ dropped
Please note that while keys must be unique, values don’t need to be. This is also the reason why there are no methods or functions for a dictionary, which permit you to find the key for a value. Suppose such a function/ method existed and further suppose that there were duplicate values for two different keys, then given a value, which key would be the correct one?
# ---ON IDLE---
>>> dictCat = {'c':'cat', 'C':'cat'}# different keys ‘c’ and ‘C’ but same value
>>> dictCat
{'c': 'cat', 'C': 'cat'}
9.4. Dictionary functions and methods
This section discusses some common dictionary functions and methods.
9.4.1. sorted(d)
Note that the sorted() function in Python can take any iterable including strings, lists and dictionaries. But for dictionaries it will return only the sorted list of keys. Hence, the return value of sorted() function is a list and not a dictionary.
# ---ON IDLE---
>>> d = { 4 : 'four', 1 : 'one', 2 :'two', 3 : 'three'}
>>> sorted(d)
[1, 2, 3, 4]
>>>
9.4.2. del d[key] (Where d is a dictionary)
Remove d[key] from d. Raises a KeyError if key is not in the map.
# ---ON IDLE---
>>> d = { 4 : 'four', 1 : 'one', 2 :'two', 3 : 'three'}
>>>del d[3]#OK since key 3 exists in d
>>> d
{1: 'one', 2: 'two', 4: 'four'}
>>>del d[3]# Error since key 3 doesn’t exist anymore in d
Traceback (most recent call last):
File "<pyshell#57>", line 1, in <module>
del d[3]
KeyError: 3
Hence, in order to avoid an error it is always better to check whether the key exists in the dictionary before using the del d[key]. ( You can always check for a key say k in dictionary say d using the syntax k in d which is True/ False depending on whether the key is present or not.
# ---ON IDLE---
>>> d = { 4 : 'four', 1 : 'one', 2 :'two', 3 : 'three'}
>>>if 3 in d:
del d[3]
>>> d
{1: 'one', 2: 'two', 4: 'four'}
>>>
9.4.3. len(d)` (Here d is a dictionary)
Return the number of items in the dictionary d. Note len() is a function. In fact, this function applies to many sequences and other collections in Python, such as strings, lists, sets, and so on.
# ---ON IDLE---
>>> d = {'x': 1, 'y': 2, 'z': 3}
>>> len(d)
3
>>>
9.5. Dictionary methods
See Page 213 of the book
9.5.1 d.clear() (where d is a dictionary)
Remove all items from the dictionary.
# ---ON IDLE---
>>> d = {'x': 1, 'y': 2, 'z': 3}
>>> d
{'y': 2, 'z': 3, 'x': 1}
>>> d.clear()
>>> d #d has no items after clear()
{}
>>>
9.5.2 d.get(some_key [, default_value]) (where d is a dictionary)
The method has two parameters out of which the second is optional.
The following code shows use of get() method without use of second parameter:
# ---ON IDLE---
>>> d = {'x': 1, 'y': 2, 'z': 3}
>>> d.get('z') # 'z' is a valid key so returns the value corresponding to this key
3
>>> d.get('a') # 'a' is an invalid key so returns a None
>>> retV = d.get('a') # retV hold the return value of the method call
>>>print(retV)
None
The following code shows use of get() method where a second parameter is also provided:-
# ---ON IDLE---
>>> d = {'x': 1, 'y': 2, 'z': 3}
>>> d.get('x', "Key not present") # key 'x' valid so value output
1
>>> d.get('a', "key not present") # key 'a' invalid so default string output
'key not present'
9.5.3 d. has_key()(where d is a dictionary)
<font color = Green> See Page 214 of the book </color>
(This method is used to check for a key. In 3.x the in operator is used.)
This is another important difference between Python 2.x and 3.x . Python 3.x does not have the has_key() method any more. It uses the in operator instead.
has_key() returns a bool value of True or False, so does the in operator. In a programming context, if you try to use a non-existent key, it will throw an error. So if you are not sure about the existence of a particular key, it is better to first check for its existence and then take action accordingly.
# ---ON IDLE---
# In python 2.x
>>> d = {'x': 1, 'y': 2, 'z': 3}
>>> d.has_key('y')
True
# In Python 3.x
>>> d = {'x': 1, 'y': 2, 'z': 3}
>>>'y' in d
True
>>>
9.5.4 d.keys()(where d is a dictionary)
keys method returns all the keys in the dictionary. Here again, there is an important difference between Python 2.x and 3.x. Suppose we have a dictionary d1. Then, in Python 2.x, the function call d1.keys() will return a list of keys, that is, keys in the form of a list (With square brackets). But in Python 3.x, it returns the keys as an iterable object instead of a list. This is much easier than it looks. What this means is that the return value of the keys() function is an object, which is iterable but which is not a list. So we have to explicitly cast it into a list using the list() function.
# ---ON IDLE---
# In python 2.x
>>> newdict = {1:'one', 2:'two', 3:'three'}
>>> newdict
{1:'one', 2:'two', 3:'three'}
>>> newdict.keys() # the keys() method returns a list
[1, 2, 3]
# In python 3.x
>>> newdict = {1:'one', 2:'two', 3:'three'}
>>> newdict
{1: 'one', 2: 'two', 3: 'three'}
>>> newdict.keys() # Does not return a list but dict_keys
dict_keys([1, 2, 3])
>>> list(newdict.keys()) # If you want a list cast into a list
[1, 2, 3]
The keys() method can be used to loop through a dictionary. Once we have the keys, we can also get the values. This is shown as follows:
# ---ON IDLE---
>>> myD = {
'k1' : 'v1',
'k2' : 'v2',
'k3' : 'v3'
}
>>>for eachK in myD:
print( eachK, '\t', myD[eachK])
k1 v1
k3 v3
k2 v2
>>>
9.5.5 d.values()(where d is a dictionary)
The values() method of a dictionary returns all the values of the dictionary. Again note, in Python 3.x, it is not a list but an iterable, which can be converted into a list by wrapping the return in a list() function call.
# ---ON IDLE---
>>> myD = {
'k1' : 'v1',
'k2' : 'v2',
'k3' : 'v3'
}
>>> myD.values() # This returns an iterable but not a list
dict_values(['v1', 'v3', 'v2'])
# python 2.x would have returned a list
>>> list(myD.values()) # In 3.x if you want a list, cast it into one
['v1', 'v3', 'v2']
# You can use the values() method also in a loop, but cant get keys from values
>>>for eachV in myD: # eachV will get the values. You cant get the keys
print(eachV)
k1
k3
k2
9.5.6 d.items()(where d is a dictionary)
The dictionary items() method returns all of the dictionary’s (key,value) pair tuples. Again as in the keys() function, there is a difference in return value in Python 2.x and 3.x.
In 2.x, the return value is automatically a list but in 3.x it is not so.
If in 3.x you want a list, you need to explicitly cast the return into a list by wrapping it in a list() function.
# ---ON IDLE---
>>> myD = {
'k1' : 'v1',
'k2' : 'v2',
'k3' : 'v3'
}
>>> myD.items() # In 3.x this does not give a list
dict_items([('k1', 'v1'), ('k3', 'v3'), ('k2', 'v2')])
# In 2.x this would give [('k1', 'v1'), ('k3', 'v3'), ('k2', 'v2')]
>>> list(myD.items())#Wrap the return from myD.items() in a list() function
[('k1', 'v1'), ('k3', 'v3'), ('k2', 'v2')]
>>>
We can use the items() method to return a tuple of key value pair. items() method can also be used to loop through a dictionary:
# ---ON IDLE---
>>> myD = {
'k1' : 'v1',
'k2' : 'v2',
'k3' : 'v3'
}
>>>for eachK, eachV in myD.items():# eachK will have the key, eachV the value
print(eachK, '\t', eachV)
k1 v1
k3 v3
k2 v2
The items() method is very useful in looping over a dictionary. Note that in a dictionary, there are two items, that is, a key and its value. When using items(), you can get both together. The following example script demonstrates this:
d = {'a': 'apple', 'b': 'berry', 'c': 'carrot'}
for k, v in d.items():
print('key->', k, 'val->', v)
9.5.7 d1.update(d2) (Where d1 and d2 are dictionaries)
The update() method is used to merge dictionaries. The method update(), merges the keys and values of one dictionary d1 into another dictionary d2. If d2 has common keys with d1, then the corresponding values of d1 are overwritten and replaced by values of d2
# ---ON IDLE---
>>> d1 = {1:'Old 1', 2:'Old 2', 3:'Old 3'}
>>> d2 = {2:'New 2', 3:'New 3', 4: 'New 4'}
>>> d1.update(d2) #Values in d1 with some keys Common to d2
# will be replaced with values of d2
>>> d1
{1: 'Old 1', 2: 'New 2', 3: 'New 3', 4: 'New 4'}
#Keys 2 and 3 are common so their values replaced from d2
9.6 Dictionary view objects
An important difference between Python 2.x and 3.x is the value or objects ‘returned’ by methods: dict.keys(), dict.values() and dict.items().
In 2.x the objects returned by these three methods are lists, but in 3.x they are view objects.
The following script will clarify this concept of a view object:
# ---ON IDLE---
>>> d1 = {1:'one', 2:'two', 3: 'three'}
>>> d1.keys()
dict_keys([1, 2, 3]) # In 3.x this is a view object
>>> list(d1.keys()) # If you want a list cast the view object into a list
[1, 2, 3]
>>> d1.values()
dict_values(['one', 'two', 'three']) # Again a view object in 3.x
>>> list(d1.values())
['one', 'two', 'three']
>>> d1.items()
dict_items([(1, 'one'), (2, 'two'), (3, 'three')]) # View object
>>> list(d1.items())
[(1, 'one'), (2, 'two'), (3, 'three')]
Beyond text book
See Page 223 of the book
2. Learning to use the specialized container Counter available in the collections module and also studying the source code of Lib/collections/ __init__.py file.
(This assignment requires understanding of OOP concepts. So it should be done after studying chapters on OOP later in the book)
Python has a module named collections, which implements what may be called specialized containers. One such container is a class called Counter with subclasses, inherited from Python dict. If it is not installed on your system, you may install it using pip command.
Every class in Python has a method called mro(), which can be used to get the base classes of a class/ object. This method mro() has been used many times in later chapters also.
The following code shows how to use the mro() method to confirm that the Counter class in collections module sub-classes dictionary, that is, dict inbuilt class of Python:
from collections import Counter
print(Counter.mro())
You can see that the Counter class indeed inherits from the dict class.
The Counter class of collections can be used to count the number of items in a collection. The items become the key and their occurrences become the values. Suppose you have a string say abcaba. It has three distinct items, that is, a, b and c.
The following script shows how you can use the Counter container:
from collections import Counter
my_str = 'abcdabcaba'
my_L = ['cat', 'dog', 'dog', 'ant', 'cat', 'dog', 'cat']
c1 = Counter(my_str)
c2 = Counter(my_L)
print(c1)
print(c2)
Now you may study the source code of file __init__.py of collections module. This file is available in your system also generally at path (If you have Anaconda):-<Path_to_Anaconda_installation>\Anaconda3\Lib\collections.
This file is also available on github at this link:- https://github.com/python/cpython/blob/3.7/Lib/collections/__init__.py
# ---ON IDLE---
class Counter(dict):
'''Dict subclass for counting hashable items. Sometimes called a bag
or multiset. Elements are stored as dictionary keys and their counts
are stored as dictionary values.
>>> c = Counter('abcdeabcdabcaba') # count elements from a string
>>> c.most_common(3) # three most common elements
[('a', 5), ('b', 4), ('c', 3)]
>>> sorted(c) # list all unique elements
['a', 'b', 'c', 'd', 'e']
>>> ''.join(sorted(c.elements())) # list elements with repetitions
'aaaaabbbbcccdde'
>>> sum(c.values()) # total of all counts
15
>>> c['a'] # count of letter 'a'
5
>>> for elem in 'shazam': # update counts from an iterable
... c[elem] += 1 # by adding 1 to each element's count
>>> c['a'] # now there are seven 'a'
7
>>> del c['b'] # remove all 'b'
>>> c['b'] # now there are zero 'b'
0
>>> d = Counter('simsalabim') # make another counter
>>> c.update(d) # add in the second counter
>>> c['a'] # now there are nine 'a'
9
>>> c.clear() # empty the counter
>>> c
Counter()
Note: If a count is set to zero or reduced to zero, it will remain
in the counter until the entry is deleted or the counter is cleared:
>>> c = Counter('aaabbc')
>>> c['b'] -= 2 # reduce the count of 'b' by two
>>> c.most_common() # 'b' is still in, but its count is zero
[('a', 3), ('c', 1), ('b', 0)]
(Only part of the output is shown above)
# ---ON IDLE---
You may perform the following tasks:
__init__() method of this class and see what kind of parameters this class takes/ can take.__missing__(self, key) method and why it is needed.most_common(), elements(), fromkeys(), update(0), and so on.
Finally, if you have a look at the docstring of the collections module, it is as follows:import collections
print(collections.__doc__)